package cn.xshi.oauth.client.util;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.filter.LevelFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import ch.qos.logback.core.spi.FilterReply;
import ch.qos.logback.core.util.FileSize;
import ch.qos.logback.core.util.OptionHelper;
import cn.xshi.oauth.client.config.LogbackConfig;
import cn.xshi.oauth.client.constant.Constant;
import cn.xshi.oauth.client.service.impl.AuthServiceImpl;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * <p>
 * logback初始化
 * </p>
 *
 * @author dengcj
 * @since 2023-04-06
 */
@Component
public class LogbackAppender {

    static org.slf4j.Logger logger = LoggerFactory.getLogger(LogbackAppender.class);

    @Resource
    SDKUtil sdkUtil;

    private static ConsoleAppender defaultConsoleAppender = null;
    /*static {
        Map<String, Appender<ILoggingEvent>> appenderMap = findAppenderList();
        Iterator<Map.Entry<String, Appender<ILoggingEvent>>> iterator = appenderMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Appender<ILoggingEvent>> entry = iterator.next();
            if (entry.getValue() instanceof ConsoleAppender) {
                // 如果logback配置文件中，已存在窗口输出的appender，则直接使用；不存在则重新生成
                defaultConsoleAppender = (ConsoleAppender) entry.getValue();
                break;
            }
        }
    }*/

    /**
     * 初始化自定义logback组件（等同于logback.xml中配置）
     */
    public boolean init() {
        boolean success = true;
        try {
            Map<String, Appender<ILoggingEvent>> appenderMap = findAppenderList();
            Iterator<Map.Entry<String, Appender<ILoggingEvent>>> iterator = appenderMap.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Appender<ILoggingEvent>> entry = iterator.next();
                if (entry.getValue() instanceof ConsoleAppender) {
                    // 如果logback配置文件中，已存在窗口输出的appender，则直接使用；不存在则重新生成
                    defaultConsoleAppender = (ConsoleAppender) entry.getValue();
                    break;
                }
            }
            createPlLogger(AuthServiceImpl.class);
        }catch (Exception e){
            success = false;
            logger.error("初始化自定义日志组件异常，信息：{}",e);
        }
        return success;
    }

    /**
     * 初始化组件自定义日志工具
     * @param clazz
     * @return
     */
    private Logger createPlLogger(Class clazz){
        LogbackConfig logbackConfig = initPLConfig();
        return createHplAppender(logbackConfig,clazz);
    }

    /**
     * 动态创建组件日志（日志组件）
     * 获取自定义的logger日志，在指定日志文件logNameEnum.getLogName()中输出日志
     * 日志中会包括所有线程及方法堆栈信息
     * @param logbackConfig
     * @param clazz
     * @return
     */
    public Logger createHplAppender(LogbackConfig logbackConfig, Class clazz) {
        Logger logger = (Logger) LoggerFactory.getLogger(clazz);

        LoggerContext loggerContext = logger.getLoggerContext();

        RollingFileAppender infoAppender = createAppender(logbackConfig, Level.INFO, loggerContext);

        ConsoleAppender realConsoleAppender = null;
        ConsoleAppender consoleAppender = defaultConsoleAppender;

        if(null == consoleAppender){//如果存在则创建控制台
            realConsoleAppender = createConsoleAppender(logbackConfig,loggerContext);
        }else{//否则赋值
            realConsoleAppender = defaultConsoleAppender;
        }

        // 设置不向上级打印信息
        logger.setAdditive(false);

        logger.addAppender(infoAppender);

        logger.addAppender(realConsoleAppender);//将配置文件中配置的也要生效
        return logger;
    }


    /**
     * 加入工具类（生成错误消息和info消息 暂不使用）
     * 获取自定义的logger日志，在指定日志文件logNameEnum.getLogName()中输出日志
     * 日志中会包括所有线程及方法堆栈信息
     * @param logbackConfig
     * @param clazz
     * @return
     */
    public Logger getLogger(LogbackConfig logbackConfig, Class clazz) {
        Logger logger = (Logger) LoggerFactory.getLogger(clazz);

        LoggerContext loggerContext = logger.getLoggerContext();

        RollingFileAppender errorAppender = createAppender(logbackConfig, Level.ERROR, loggerContext);

        RollingFileAppender infoAppender = createAppender(logbackConfig, Level.INFO, loggerContext);

        ConsoleAppender consoleAppender = defaultConsoleAppender;

        ConsoleAppender realConsoleAppender = null;

        if(null == consoleAppender){//如果存在则创建控制台
            realConsoleAppender = createConsoleAppender(logbackConfig,loggerContext);
        }else{//否则赋值
            realConsoleAppender = defaultConsoleAppender;
        }

        // 设置不向上级打印信息
        logger.setAdditive(false);
        logger.addAppender(errorAppender);
        logger.addAppender(infoAppender);
        logger.addAppender(realConsoleAppender);
        return logger;
    }

    /**
     * 创建日志文件的file appender
     * @param logbackConfig
     * @param level
     * @param loggerContext
     * @return
     */
    private static RollingFileAppender createAppender(LogbackConfig logbackConfig, Level level, LoggerContext loggerContext) {

        RollingFileAppender appender = new RollingFileAppender();

        appender.addFilter(createLevelFilter(level));// 这里设置级别过滤器

        // 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
        // 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
        appender.setContext(loggerContext);

        // appender的name属性
        appender.setName(logbackConfig.getFileName() + "-" + level.levelStr.toUpperCase());

        // 读取logback配置文件中的属性值，设置文件名
        // String logPath = OptionHelper.substVars("${logPath}-" + name + "-" + level.levelStr.toLowerCase() + ".log", loggerContext);
        String logPath = OptionHelper.substVars(logbackConfig.getFileFull(), loggerContext);
        appender.setFile(logPath);
        appender.setAppend(true);
        appender.setPrudent(false);

        // 加入下面两个节点
        appender.setRollingPolicy(createRollingPolicy(logbackConfig, loggerContext, appender));
        appender.setEncoder(createEncoder(logbackConfig,loggerContext));
        appender.start();
        return appender;
    }

    /**
     * 创建窗口输入的appender
     * @param loggerContext
     * @return
     */
    private ConsoleAppender createConsoleAppender(LogbackConfig logbackConfig,LoggerContext loggerContext) {
        ConsoleAppender appender = new ConsoleAppender();

        appender.setContext(loggerContext);

        appender.setName(sdkUtil.getProjectName());//工程（服务名）

        appender.addFilter(createLevelFilter(Level.DEBUG));//创建打印日志的级别

        appender.setEncoder(createEncoder(logbackConfig,loggerContext));//创建编码

        appender.start();

        return appender;
    }

    /**
     * 设置日志的滚动策略（按日期+大小滚动,兼容老版本）
     * @param logbackConfig
     * @param context
     * @param appender
     * @return
     */
    private static TimeBasedRollingPolicy createRollingPolicy(LogbackConfig logbackConfig, LoggerContext context, FileAppender appender) {

        // 读取logback配置文件中的属性值，设置文件名
        String fp = OptionHelper.substVars(logbackConfig.getFileNamePattern(), context);
        TimeBasedRollingPolicy rollingPolicyBase = new TimeBasedRollingPolicy();

        // 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
        // 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
        rollingPolicyBase.setContext(context);

        rollingPolicyBase.setParent(appender);// 设置父节点是appender

        rollingPolicyBase.setFileNamePattern(fp);// 设置文件名模式

        //兼容老版本
        SizeAndTimeBasedFNATP sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP();
        sizeAndTimeBasedFNATP.setMaxFileSize(FileSize.valueOf(logbackConfig.getMaxFileSize()));// 最大日志文件大小
        rollingPolicyBase.setTimeBasedFileNamingAndTriggeringPolicy(sizeAndTimeBasedFNATP);

        rollingPolicyBase.setMaxHistory(logbackConfig.getMaxHistory()); // 设置最大历史记录为N天

        rollingPolicyBase.setTotalSizeCap(FileSize.valueOf(logbackConfig.getTotalSizeCap())); // 总大小限制
        rollingPolicyBase.start();

        return rollingPolicyBase;
    }

    /**
     * 设置日志的滚动策略（按大小+日期滚动 新版本）
     * @param logbackConfig
     * @param context
     * @param appender
     * @return
     */
    /*
    private static TimeBasedRollingPolicy createRollingPolicy(LogbackConfig logbackConfig, LoggerContext context, FileAppender appender) {

        // 读取logback配置文件中的属性值，设置文件名
        String fp = OptionHelper.substVars(logbackConfig.getFileNamePattern(), context);
        SizeAndTimeBasedRollingPolicy sizeAndTimeBasedRollingPolicy = new SizeAndTimeBasedRollingPolicy();

        // 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
        // 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
        sizeAndTimeBasedRollingPolicy.setContext(context);

        sizeAndTimeBasedRollingPolicy.setParent(appender);// 设置父节点是appender

        sizeAndTimeBasedRollingPolicy.setFileNamePattern(fp);// 设置文件名模式

        sizeAndTimeBasedRollingPolicy.setMaxFileSize(FileSize.valueOf(logbackConfig.getMaxFileSize()));//最大日志文件大小
        sizeAndTimeBasedRollingPolicy.setMaxHistory(logbackConfig.getMaxHistory()); // 设置最大历史记录为N天

        sizeAndTimeBasedRollingPolicy.setTotalSizeCap(FileSize.valueOf(logbackConfig.getTotalSizeCap())); // 总大小限制
        sizeAndTimeBasedRollingPolicy.start();

        return sizeAndTimeBasedRollingPolicy;
    }*/

    /**
     * 设置日志的输出格式
     * @param context
     * @return
     */
    private static PatternLayoutEncoder createEncoder(LogbackConfig logbackConfig,LoggerContext context) {
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        // 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
        // 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
        encoder.setContext(context);
        // 设置格式
//        String pattern = OptionHelper.substVars("${pattern}", context);
        String pattern = OptionHelper.substVars(logbackConfig.getContentPattern(), context);
        encoder.setPattern(pattern);
        encoder.setCharset(Charset.forName("utf-8"));
        encoder.start();
        return encoder;
    }

    /**
     * 设置打印日志的级别
     * @param level
     * @return
     */
    private static Filter createLevelFilter(Level level) {
        LevelFilter levelFilter = new LevelFilter();
        levelFilter.setLevel(level);
        levelFilter.setOnMatch(FilterReply.ACCEPT);
        levelFilter.setOnMismatch(FilterReply.DENY);
        levelFilter.start();
        return levelFilter;
    }

    /**
     * 查找当前SDK所在应用下Logback全部配置（包含Appender）
     * @return
     */
    private static Map<String, Appender<ILoggingEvent>> findAppenderList() {
        Map<String, Appender<ILoggingEvent>> appenderMap = new HashMap<String, Appender<ILoggingEvent>>();
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        for (Logger logger : context.getLoggerList()) {
            for (Iterator<Appender<ILoggingEvent>> index = logger.iteratorForAppenders(); index.hasNext(); ) {
                Appender<ILoggingEvent> appender = index.next();
                appenderMap.put(appender.getName(), appender);
            }
        }
        return appenderMap;
    }

    /**
     * 初始化日志组件配置
     * @return
     */
    private LogbackConfig initPLConfig(){
        LogbackConfig logbackConfig = new LogbackConfig();
        String projectName = sdkUtil.getProjectName();//SDK所在应用名
        String logFilePath = sdkUtil.getLogPath();      //SDK所在应用路径
        String logIpFile = SDKUtil.getLocalIp();    //SDK所在服务ip
        logbackConfig.setFilePath(logFilePath);
        logbackConfig.setFileName(projectName);
        logbackConfig.setFileNamePattern(logFilePath+"/"+ Constant.AUTH_FILE_NAME+projectName+"-"+logIpFile+"@%d.%i.log");//每日产生的日志文件命名格式规则（如：${logFilePath}/auth-${logFileName}-${logIpFile}@%d.%i.log）
        logbackConfig.setFileFull(logFilePath+"/"+Constant.AUTH_FILE_NAME+projectName+"-"+logIpFile+".log");//${logFilePath}/auth-${logFileName}-${logIpFile}.log
//        logbackConfig.setMaxFileSize("1M"); //最大日志文件大小 1M
//        logbackConfig.setMaxHistory(2);//设置最大历史记录为2天
//        logbackConfig.setTotalSizeCap("100GB");//总大小限制
        logbackConfig.setContentPattern("%msg%n");
        return logbackConfig;
    }
}
